package heap

import "fmt"
import "jvmgo/ch06/classfile"
import "jvmgo/ch06/classpath"

/*
class names:
    - primitive types: boolean, byte, int ...
    - primitive arrays: [Z, [B, [I ...
    - non-array classes: java/lang/Object ...
    - array classes: [Ljava/lang/Object; ...
*/
type ClassLoader struct {
    cp       *classpath.Classpath
    classMap map[string]*Class // loaded classes
}

func NewClassLoader(cp *classpath.Classpath) *ClassLoader {
    return &ClassLoader{
        cp:       cp,
        classMap: make(map[string]*Class),
    }
}

func (self *ClassLoader) LoadClass(name string) *Class {
    if class, ok := self.classMap[name]; ok {
        // already loaded
        return class
    }

    return self.loadNonArrayClass(name)
}

func (self *ClassLoader) loadNonArrayClass(name string) *Class {
    //查找class文件
    data, entry := self.readClass(name)

    //解析class文件
    class := self.defineClass(data)

    //链接
    link(class)
    fmt.Printf("[Loaded %s from %s]\n", name, entry)
    return class
}

func (self *ClassLoader) readClass(name string) ([]byte, classpath.Entry) {
    data, entry, err := self.cp.ReadClass(name)
    if err != nil {
        panic("java.lang.ClassNotFoundException: " + name)
    }
    return data, entry
}

// jvms 5.3.5
func (self *ClassLoader) defineClass(data []byte) *Class {
    class := parseClass(data)
    class.loader = self
    resolveSuperClass(class)
    resolveInterfaces(class)
    self.classMap[class.name] = class
    return class
}

//解析class文件，得到ClassFile结构体
func parseClass(data []byte) *Class {
    cf, err := classfile.Parse(data)
    if err != nil {
        //panic("java.lang.ClassFormatError")
        panic(err)
    }
    return newClass(cf)
}

// jvms 5.4.3.1
func resolveSuperClass(class *Class) {
    if class.name != "java/lang/Object" {
        class.superClass = class.loader.LoadClass(class.superClassName)
    }
}
func resolveInterfaces(class *Class) {
    interfaceCount := len(class.interfaceNames)
    if interfaceCount > 0 {
        class.interfaces = make([]*Class, interfaceCount)
        for i, interfaceName := range class.interfaceNames {
            class.interfaces[i] = class.loader.LoadClass(interfaceName)
        }
    }
}

func link(class *Class) {
    verify(class)
    prepare(class)
}

func verify(class *Class) {
    // todo 验证阶段在虚拟机规范4.10节
}

// jvms 5.4.2
func prepare(class *Class) {
    calcInstanceFieldSlotIds(class)
    calcStaticFieldSlotIds(class)
    allocAndInitStaticVars(class)
}

//计算实例字段个数，并编号
func calcInstanceFieldSlotIds(class *Class) {
    slotId := uint(0)
    if class.superClass != nil {
        slotId = class.superClass.instanceSlotCount
    }
    for _, field := range class.fields {
        if !field.IsStatic() {
            field.slotId = slotId
            slotId++
            if field.isLongOrDouble() {
                slotId++
            }
        }
    }
    class.instanceSlotCount = slotId
}

//计算静态字段个数，并编号
func calcStaticFieldSlotIds(class *Class) {
    slotId := uint(0)
    for _, field := range class.fields {
        if field.IsStatic() {
            field.slotId = slotId
            slotId++
            if field.isLongOrDouble() {
                slotId++
            }
        }
    }
    class.staticSlotCount = slotId
}

//给类变量分配空间，并初始化
func allocAndInitStaticVars(class *Class) {
    class.staticVars = newSlots(class.staticSlotCount)
    for _, field := range class.fields {
        if field.IsStatic() && field.IsFinal() {
            initStaticFinalVar(class, field)
        }
    }
}

//从常量池中加载常量池，然后给静态变量赋值
func initStaticFinalVar(class *Class, field *Field) {
    vars := class.staticVars
    cp := class.constantPool
    cpIndex := field.ConstValueIndex()
    slotId := field.SlotId()

    if cpIndex > 0 {
        switch field.Descriptor() {
        case "Z", "B", "C", "S", "I":
            val := cp.GetConstant(cpIndex).(int32)
            vars.SetInt(slotId, val)
        case "J":
            val := cp.GetConstant(cpIndex).(int64)
            vars.SetLong(slotId, val)
        case "F":
            val := cp.GetConstant(cpIndex).(float32)
            vars.SetFloat(slotId, val)
        case "D":
            val := cp.GetConstant(cpIndex).(float64)
            vars.SetDouble(slotId, val)
        case "Ljava/lang/String;":
            panic("todo")
        }
    }
}
